package com.deductive.netty.tcp.handler;

import com.deductive.netty.tcp.message.TcpMessage;
import com.deductive.netty.websocket.service.NettyConnectionManager;
import com.deductive.netty.websocket.service.WebSocketPushService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

@Component
@ChannelHandler.Sharable
public class TcpMessageHandler extends SimpleChannelInboundHandler<String> { // Changed to handle String

    private static final Logger logger = LoggerFactory.getLogger(TcpMessageHandler.class);
    private static final ObjectMapper objectMapper = new ObjectMapper();

    @Autowired
    private WebSocketPushService webSocketPushService;

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String jsonString) throws Exception {
        TcpMessage tcpMessage;
        try {
            tcpMessage = objectMapper.readValue(jsonString, TcpMessage.class);
        } catch (JsonProcessingException e) {
            logger.error("Failed to parse TCP JSON message: {}", jsonString, e);
            sendMessage(ctx, createErrorResponse("Invalid JSON format"));
            return;
        }

        String userId = NettyConnectionManager.getUserId(ctx.channel());

        switch (tcpMessage.getType()) {
            case "AUTH":
                handleAuth(ctx, tcpMessage);
                break;
            case "HEARTBEAT":
                handleHeartbeat(ctx);
                break;
            case "DATA":
                if (userId == null) {
                    sendMessage(ctx, createErrorResponse("Unauthorized. Please authenticate first."));
                    ctx.close();
                    return;
                }
                handleData(ctx, userId, tcpMessage);
                break;
            default:
                logger.warn("Received unknown TCP message type: {}", tcpMessage.getType());
                sendMessage(ctx, createErrorResponse("Unknown message type: " + tcpMessage.getType()));
                break;
        }
    }

    private void handleAuth(ChannelHandlerContext ctx, TcpMessage message) {
        JsonNode payload = message.getPayload();
        if (payload != null && payload.has("userId")) {
            String userId = payload.get("userId").asText();

            if (userId.isEmpty()) {
                sendMessage(ctx, createAuthResponse(false, "User ID cannot be empty."));
                ctx.close();
                return;
            }

            NettyConnectionManager.addChannel(userId, ctx.channel());
            logger.info("TCP user {} authenticated on channel {}", userId, ctx.channel().id());
            sendMessage(ctx, createAuthResponse(true, "Authentication successful."));

            // Broadcast user online status
            webSocketPushService.broadcastUserOnline(userId);
        } else {
            sendMessage(ctx, createAuthResponse(false, "Authentication failed. Missing userId in payload."));
            ctx.close();
        }
    }

    private void handleHeartbeat(ChannelHandlerContext ctx) {
        logger.debug("Received heartbeat from channel {}", ctx.channel().id());
        Map<String, Object> response = new HashMap<>();
        response.put("type", "HEARTBEAT_REPLY");
        response.put("payload", Collections.singletonMap("timestamp", System.currentTimeMillis()));
        sendMessage(ctx, response);
    }

    private void handleData(ChannelHandlerContext ctx, String userId, TcpMessage message) {
        logger.info("Received DATA message from user {}: {}", userId, message.getPayload());
        // Echo the message back for now
        Map<String, Object> response = new HashMap<>();
        response.put("type", "ECHO");
        response.put("payload", message.getPayload());
        sendMessage(ctx, response);
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        String userId = NettyConnectionManager.getUserId(ctx.channel());
        if (userId != null) {
            NettyConnectionManager.removeChannel(ctx.channel());
            logger.info("TCP user {} disconnected, channel {} removed", userId, ctx.channel().id());
            webSocketPushService.broadcastUserOffline(userId);
        } else {
            logger.info("Unauthenticated TCP channel {} disconnected.", ctx.channel().id());
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        logger.error("Exception caught on TCP channel {}", ctx.channel().id(), cause);
        handlerRemoved(ctx);
        ctx.close();
    }

    private void sendMessage(ChannelHandlerContext ctx, Object messageObject) {
        try {
            String jsonResponse = objectMapper.writeValueAsString(messageObject);
            ctx.writeAndFlush(jsonResponse + "\n");
        } catch (JsonProcessingException e) {
            logger.error("Failed to serialize response message for channel {}", ctx.channel().id(), e);
        }
    }

    private Map<String, Object> createErrorResponse(String errorMessage) {
        Map<String, Object> response = new HashMap<>();
        response.put("type", "ERROR");
        response.put("payload", Collections.singletonMap("message", errorMessage));
        return response;
    }

    private Map<String, Object> createAuthResponse(boolean success, String message) {
        Map<String, Object> payload = new HashMap<>();
        payload.put("success", success);
        payload.put("message", message);

        Map<String, Object> response = new HashMap<>();
        response.put("type", success ? "AUTH_SUCCESS" : "AUTH_FAILED");
        response.put("payload", payload);
        return response;
    }
}
